﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using NLite.Reflection;
using System.Collections.Specialized;
using NLite.Validation;

namespace NLite.Mapping.Internal
{
	class ClassToDictionaryMapper : MapperBase
	{
		
		public ClassToDictionaryMapper(Type fromType, Type toType)
			: base(fromType, toType)
		{
			
		}


		public override void Map(ref object from, ref object to)
		{
			var members = _Info
				.SourceMembers
				.Value
				.Where(m => m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property)
				.ToArray();

			if (to == null)
				to = new Dictionary<string, object>(members.Length);

			var dic = to as IDictionary<string,object>;
			foreach (var m in members)
				dic[m.Name] = m.Get(from);

			to = dic;
		}
	}

	class DictonaryToClassMapper : MapperBase
	{
		public DictonaryToClassMapper(Type fromType, Type toType)
			: base(fromType, toType)
		{
		}


		public override void Map(ref object from, ref object to)
		{
			if (to == null)
				to = ObjectCreator.Create(_Info.To);

			if (_Info.From.IsDictionaryType())
			{
				var dicType = _Info.From.GetGenericDictionaryType();
				var keyType = dicType.GetGenericArguments()[0];
				var valueType = dicType.GetGenericArguments()[1];

				if (keyType == Types.String)
				{
					if (valueType == Types.String)
						new DictionaryOfStringAndString { State = this.State }.Map(ref _Info, ref from, ref to);
					else
						new DictionaryOfStringAndObject { State = this.State }.Map(ref _Info, ref from, ref to);
				}
			}
			else if (Types.StringDictionary.IsAssignableFrom( _Info.From) )
			{
				var dic = from as StringDictionary;
				var tmpFrom = new Dictionary<string, string>(dic.Count);
				foreach (string k in dic.Keys)
					tmpFrom.Add(k, dic[k]);

				new DictionaryOfStringAndString { State = this.State }.Map(ref _Info, ref from, ref to);

				tmpFrom.Clear();
				tmpFrom = null;
			}
			else if (Types.NameValueCollection.IsAssignableFrom(_Info.From))
			{
				var dic = from as NameValueCollection ;
				var tmpFrom = new Dictionary<string, string>(dic.Count);
				foreach (string k in dic.Keys)
					tmpFrom.Add(k, dic[k]);

				new DictionaryOfStringAndString { State = this.State }.Map(ref _Info, ref from, ref to);

				tmpFrom.Clear();
				tmpFrom = null;
			}
		}

		interface IDictionaryMapper
		{
			void Map(ref MapperInfo _Info, ref object from, ref object to);
		}

		class DictionaryMapper<TValue> : IDictionaryMapper
		{
			public IErrorState State;
			public void Map(ref MapperInfo _Info, ref object from, ref object to)
			{
				var dic = from as IDictionary<string, TValue>;
				IDictionary<string, TValue> tmpDic = dic;

				if (_Info.IgnoreCase)
					tmpDic = new Dictionary<string, TValue>(dic, _Info.IgnoreCase ? StringComparer.InvariantCultureIgnoreCase : StringComparer.InvariantCulture);

				var tmpMembers = _Info
					.DestinationMembers
					.Value
					.Where(m => m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property)
					.ToArray();

				var members = tmpMembers
					.Where(m => tmpDic.ContainsKey(m.Name))
					.Select(m => new { Member = m, SetMember = m.GetSetter<object,object>(), Value = tmpDic[m.Name] });

				if (_Info.IgnoreUnderscore)
				{
					members = (
						from m in tmpMembers
						let name = m.Name.Replace("_", string.Empty)
						where tmpDic.ContainsKey(name)
                        select new { Member = m, SetMember = m.GetSetter<object, object>(), Value = tmpDic[name] })
						.Union(members);
				}

				

				foreach (var item in members)
				{
					try
					{
						object value = item.Value;
						var memberType = item.Member.GetMemberType();
						
						if (value != null)
						{
							var valueType = value.GetType();
							var attr = item.Member.GetAttribute<SpliteAttribute>(true);
							
							if(valueType == Types.String
							   && memberType.IsArray
							   && attr != null)
							{
								var separator = attr.Separator;
								if(separator != null && separator.Length > 0)
									value = (value as string).Split(separator);
								else
									value = new string[]{ value as string};
							}
							//else
							value = Mapper.Map(value, valueType, memberType);
						}
						else
							value = ObjectCreator.Create(memberType);

                        item.SetMember(to, value);
					}
					catch (Exception ex)
					{
						State.AddError(item.Member.Name, ex.GetBaseException().Message);
					}
				}
				tmpDic.Clear();
				tmpDic = null;
			}

			
		}

		class DictionaryOfStringAndString : DictionaryMapper<string> { }
		class DictionaryOfStringAndObject : DictionaryMapper<Object> { }
	}
}
